/*
 * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
 *
 * Copyright (C) 2016-2023 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitee.wsl.coroutines.job.trigger.impl

import com.gitee.wsl.coroutines.job.IJobExecutionContext
import com.gitee.wsl.coroutines.job.JobDataMap
import com.gitee.wsl.coroutines.job.JobKey
import com.gitee.wsl.coroutines.job.exception.JobExecutionException
import com.gitee.wsl.coroutines.job.exception.SchedulerException
import com.gitee.wsl.coroutines.job.sheduler.IScheduler
import com.gitee.wsl.coroutines.job.trigger.IOperableTrigger
import com.gitee.wsl.coroutines.job.trigger.IScheduleBuilder
import com.gitee.wsl.coroutines.job.trigger.ITrigger
import com.gitee.wsl.coroutines.job.trigger.ITrigger.Companion.DEFAULT_PRIORITY
import com.gitee.wsl.coroutines.job.trigger.TriggerBuilder
import com.gitee.wsl.coroutines.job.trigger.TriggerKey
import com.gitee.wsl.ext.base.compare
import kotlin.jvm.Transient
import kotlin.time.ExperimentalTime

/**
 *
 *
 * The base abstract class to be extended by all `Trigger`s.
 *
 *
 *
 * `Triggers` s have a name and group associated with them, which
 * should uniquely identify them within a single
 * `[IScheduler]`.
 *
 *
 *
 * `Trigger`s are the 'mechanism' by which `Job` s are
 * scheduled. Many `Trigger` s can point to the same
 * `Job`, but a single `Trigger` can only point to one
 * `Job`.
 *
 *
 *
 * Triggers can 'send' parameters/data to `Job`s by placing contents
 * into the `JobDataMap` on the `Trigger`.
 *
 *
 * @author James House
 * @author Sharada Jambula
 * @param <IMPLTYPE>
 * Implementation type
</IMPLTYPE> */
abstract class AbstractTrigger<IMPLTYPE : AbstractTrigger<IMPLTYPE>> : IOperableTrigger{

    private var m_sName: String? = null
    private var m_sGroup: String? = IScheduler.DEFAULT_GROUP
    private var m_sJobName: String? = null
    private var m_sJobGroup: String? = IScheduler.DEFAULT_GROUP
    
    override var description: String? = null
    private var m_aJobDataMap: JobDataMap? = null
    
    override var calendarName: String? = null
    
    override var fireInstanceId: String? = null
    
    private var m_eMisfireInstruction: ITrigger.EMisfireInstruction? = ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_SMART_POLICY

    override var priority: Int = DEFAULT_PRIORITY

    @Transient
    private var m_aKey: TriggerKey? = null

    /**
     * Copy constructor
     *
     * @param aOther
     * Calendar to copy from. May not be `null`.
     */
   /* protected constructor(aOther: AbstractTrigger<IMPLTYPE>) {
        
        m_sName = aOther.m_sName
        m_sGroup = aOther.m_sGroup
        m_sJobName = aOther.m_sJobName
        m_sJobGroup = aOther.m_sJobGroup
        description = aOther.description
        // Shallow copy the jobDataMap. Note that this means that if a user
        // modifies a value object in this map from the cloned Trigger
        // they will also be modifying this Trigger.
        m_aJobDataMap = aOther.m_aJobDataMap
        calendarName = aOther.calendarName
        fireInstanceId = aOther.fireInstanceId
        m_eMisfireInstruction = aOther.m_eMisfireInstruction
        priority = aOther.priority
        m_aKey = aOther.m_aKey
    }*/

    /**
     *
     *
     * Create a `Trigger` with no specified name, group, or
     * `[com.helger.quartz.IJobDetail]`.
     *
     *
     *
     * Note that the [.setName],[.setGroup]and the
     * [.setJobName]and [.setJobGroup]methods must be
     * called before the `Trigger` can be placed into a
     * [IScheduler].
     *
     */
    constructor()


    /**
     *
     *
     * Create a `Trigger` with the given name, and group.
     *
     *
     *
     * Note that the [.setJobName]and
     * [.setJobGroup]methods must be called before the
     * `Trigger` can be placed into a [IScheduler].
     *
     *
     * @param group
     * if `null`, Scheduler.DEFAULT_GROUP will be used.
     * @exception IllegalArgumentException
     * if name is null or empty, or the group is an empty string.
     */
    constructor(name: String, group: String?=null) {
        this.name = name
        this.group = group
    }

    /**
     *
     *
     * Create a `Trigger` with the given name, and group.
     *
     *
     * @param group
     * if `null`, Scheduler.DEFAULT_GROUP will be used.
     * @exception IllegalArgumentException
     * if name is null or empty, or the group is an empty string.
     */
    constructor(name: String, group: String?, jobName: String, jobGroup: String?) {
        this.name = name
        this.group = group
        this.jobName = jobName
        this.jobGroup = jobGroup
    }

    var name: String?
        /**
         * Get the name of this `Trigger`.
         */
        get() = m_sName
        /**
         *
         *
         * Set the name of this `Trigger`.
         *
         *
         * @exception IllegalArgumentException
         * if name is null or empty.
         */
        set(name) {

            require(name!!.trim { it <= ' ' }.isNotEmpty()) { "Trigger name cannot be null or empty." }

            m_sName = name
            m_aKey = null
        }

    var group: String?
        /**
         * Get the group of this `Trigger`.
         */
        get() = m_sGroup
        /**
         * Set the name of this `Trigger`.
         *
         * @param group
         * if `null`, Scheduler.DEFAULT_GROUP will be used.
         * @exception IllegalArgumentException
         * if group is an empty string.
         */
        set(group) {
            if (group != null)
                require(group.trim { it <= ' ' }.isNotEmpty()){"Group name cannot be an empty string."}

            m_sGroup = group ?: IScheduler.DEFAULT_GROUP
            m_aKey = null
        }

    var jobName: String?
        /**
         *
         *
         * Get the name of the associated
         * `[com.helger.quartz.IJobDetail]`.
         *
         */
        get() = m_sJobName
        /**
         *
         *
         * Set the name of the associated
         * `[com.helger.quartz.IJobDetail]`.
         *
         *
         * @exception IllegalArgumentException
         * if jobName is null or empty.
         */
        set(jobName) {
           require(jobName!!.trim { it <= ' ' }.isNotEmpty()){ "Job name cannot be null or empty."}

            m_sJobName = jobName
        }

    var jobGroup: String?
        /**
         *
         *
         * Get the name of the associated
         * `[com.helger.quartz.IJobDetail]`'s group.
         *
         */
        get() = m_sJobGroup
        /**
         *
         *
         * Set the name of the associated
         * `[com.helger.quartz.IJobDetail]`'s group.
         *
         *
         * @param jobGroup
         * if `null`, Scheduler.DEFAULT_GROUP will be used.
         * @exception IllegalArgumentException
         * if group is an empty string.
         */
        set(jobGroup) {
            if (jobGroup != null)
                require(jobGroup.trim { it <= ' ' }.isNotEmpty()){"Group name cannot be null or empty."}

            m_sJobGroup = jobGroup ?: IScheduler.DEFAULT_GROUP
        }

    
    val fullName: String
        /**
         * Returns the 'full name' of the `Trigger` in the format
         * "group.name".
         */
        get() = "$m_sGroup.$m_sName"

    
    override var key: TriggerKey?
        get() {
            if (m_aKey == null) {
                if (m_sName == null) return null
                m_aKey = TriggerKey(m_sName!!, m_sGroup)
            }

            return m_aKey
        }
        set(key) {
            name  = key?.name
            group = key?.group
            m_aKey = key
        }

    
    override var jobKey: JobKey?
        get() {
            if (jobName == null) return null
            return JobKey(jobName!!, jobGroup)
        }
        set(key) {
            jobName  = key?.name
            jobGroup = key?.group
        }

    
    val fullJobName: String
        /**
         * Returns the 'full name' of the `Job` that the
         * `Trigger` points to, in the format "group.name".
         */
        get() = "$m_sJobGroup.$m_sJobName"

    
    override var jobDataMap: JobDataMap
        get() {
            if (m_aJobDataMap == null) m_aJobDataMap = JobDataMap()
            return m_aJobDataMap!!
        }
        set(jobDataMap) {
            m_aJobDataMap = jobDataMap
        }

    /**
     * This method should not be used by the Quartz client.<br></br>
     * Called after the `[IScheduler]` has executed the
     * `[com.helger.quartz.IJobDetail]` associated with the
     * `Trigger` in order to get the final instruction code from the
     * trigger.
     *
     * @param context
     * is the `JobExecutionContext` that was used by the
     * `Job`'s`execute(xx)` method.
     * @param result
     * is the `JobExecutionException` thrown by the
     * `Job`, if any (may be null).
     * @return one of the CompletedExecutionInstruction constants.
     * @see com.helger.quartz.ITrigger.ECompletedExecutionInstruction
     *
     * @see .triggered
     */
    override fun executionComplete(
        context: IJobExecutionContext,
        result: JobExecutionException?
    ): ITrigger.ECompletedExecutionInstruction {
        if (result != null) {
            if (result.refire) return ITrigger.ECompletedExecutionInstruction.RE_EXECUTE_JOB
            if (result.unscheduleTrigg) return ITrigger.ECompletedExecutionInstruction.SET_TRIGGER_COMPLETE
            if (result.unscheduleAllTriggs) return ITrigger.ECompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_COMPLETE
        }

        if (!mayFireAgain()) return ITrigger.ECompletedExecutionInstruction.DELETE_TRIGGER

        return ITrigger.ECompletedExecutionInstruction.NOOP
    }

    override var misfireInstruction: ITrigger.EMisfireInstruction?
        get() = m_eMisfireInstruction
        set(misfireInstruction) {
            if (!validateMisfireInstruction(misfireInstruction)) throw IllegalArgumentException(
                "The misfire instruction code is invalid for this type of trigger."
            )

            m_eMisfireInstruction = misfireInstruction

        }

    protected abstract fun validateMisfireInstruction(candidateMisfireInstruction: ITrigger.EMisfireInstruction?): Boolean

    /**
     *
     *
     * Validates whether the properties of the `JobDetail` are valid
     * for submission into a `Scheduler`.
     *
     * @throws IllegalStateException
     * if a required property (such as Name, Group, Class) is not set.
     */
    @Throws(SchedulerException::class)
    override fun validate() {
        if (m_sName == null) throw SchedulerException("Trigger's name cannot be null")

        if (m_sGroup == null) throw SchedulerException("Trigger's group cannot be null")

        if (m_sJobName == null) throw SchedulerException("Trigger's related Job's name cannot be null")

        if (m_sJobGroup == null) throw SchedulerException("Trigger's related Job's group cannot be null")
    }

    /**
     *
     *
     * Return a simple string representation of this object.
     *
     */
    @OptIn(ExperimentalTime::class)
    override fun toString(): String {
        return "Trigger '" +
                fullName +
                "':  triggerClass: '" +
                this::class.simpleName +
                " calendar: '" +
                calendarName +
                "' misfireInstruction: " +
                misfireInstruction +
                " nextFireTime: " +
                nextFireTime
    }

    /**
     *
     *
     * Compare the next fire time of this `Trigger` to that of another
     * by comparing their keys, or in other words, sorts them according to the
     * natural (i.e. alphabetical) order of their keys.
     *
     */
    override fun compareTo(other: ITrigger): Int {
        return key.compare(other = other.key){ v1, v2->
            v1.name.compareTo(v2.name)
        }
        //return CompareHelper.compare(key, aOther.key, false)
    }


    @OptIn(ExperimentalTime::class)
    override val triggerBuilder: TriggerBuilder<IMPLTYPE>
        get() = TriggerBuilder.newTrigger<IMPLTYPE>()
            .forJob(jobKey)
            .modifiedByCalendar(calendarName?:"")
            .usingJobData(jobDataMap)
            .withDescription(description?:"")
            .endAt(endTime)
            .withIdentity(key)
            .withPriority(priority)
            .startAt(startTime)
            .withSchedule(scheduleBuilder)

    
    abstract override val scheduleBuilder: IScheduleBuilder<IMPLTYPE>


}
